﻿using System;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.SocketInterfaces;

namespace Gadgeteer.Modules.GHIElectronics
{
    // -- CHANGE FOR MICRO FRAMEWORK 4.2 --
    // If you want to use Serial, SPI, or DaisyLink (which includes GTI.SoftwareI2CBus), you must do a few more steps
    // since these have been moved to separate assemblies for NETMF 4.2 (to reduce the minimum memory footprint of Gadgeteer)
    // 1) add a reference to the assembly (named Gadgeteer.[interfacename])
    // 2) in GadgeteerHardware.xml, uncomment the lines under <Assemblies> so that end user apps using this module also add a reference.

    /// <summary>
    /// A 125Khz RFID reader module for Microsoft .NET Gadgeteer
    /// </summary>
    /// <example>
    /// <para>The following example uses a <see cref="RFID"/> object to display the ID of a swiped RFID card.</para>
    /// <code>
    /// using System;
    /// using System.Collections;
    /// using System.Threading;
    /// using Microsoft.SPOT;
    /// using Microsoft.SPOT.Presentation;
    /// using Microsoft.SPOT.Presentation.Controls;
    /// using Microsoft.SPOT.Presentation.Media;
    /// using Microsoft.SPOT.Touch;
    ///
    /// using Gadgeteer.Networking;
    /// using GT = Gadgeteer;
    /// using GTM = Gadgeteer.Modules;
    /// using Gadgeteer.Modules.GHIElectronics;
    ///
    /// namespace TestApp
    /// {
    ///     public partial class Program
    ///     {
    ///         void ProgramStarted()
    ///         {
    ///             rfid.CardIDReceived += new RFID.CardIDReceivedEventHandler(rfid_CardIDReceived);
    ///         }
    ///
    ///         void rfid_CardIDReceived(RFID sender, string ID)
    ///         {
    ///             Debug.Print(ID);
    ///         }
    ///     }
    /// }
    /// </code>
    /// </example>
    public class RFID : GTM.Module
    {
        private GT.SocketInterfaces.Serial serialPort;

        private const int ID_LENGTH = 12;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public RFID(int socketNumber)
        {
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);

            socket.EnsureTypeIsSupported('U', this);

            serialPort = GTI.SerialFactory.Create(socket, 9600, GTI.SerialParity.None, GTI.SerialStopBits.Two, 8, GTI.HardwareFlowControl.NotRequired, this);
            serialPort.Open();

            System.Threading.Thread readThread = new System.Threading.Thread(_readThreadStart);

            readThread.Start();
        }

        private byte ASCIIToNum(char upper, char lower)
        {
            byte high, low;

            if (upper >= 'A')
                high = (byte)(upper - 48  - 7);
            else
                high = (byte)(upper - 48);

            if (lower >= 'A')
                low = (byte)(lower - 48 - 7);
            else
                low = (byte)(lower - 48);

            byte num = 0;
            num = (byte)(high << 4);
            num = (byte)(num | (low));
            return num;
        }

        private string cardID = new string(null);

        void _readThreadStart()
        {
            int bytesToRead = 0;
            byte[] readBuffer;

            cardID = new string(null);

            for (; ; )
            {
                bytesToRead = serialPort.BytesToRead;

                if (bytesToRead > 0)
                {
                    readBuffer = new byte[bytesToRead];

                    serialPort.Read(readBuffer, 0, bytesToRead);

                    for (int i = 0; i < readBuffer.Length; i++)
                    {
                        cardID += (char)readBuffer[i];

                        if (cardID.Length == ID_LENGTH)
                        {
                            if (cardID[0] != 2)
                            {
                                HandleChecksumError();
                                break;
                            }

                            //verify checksum
                            int cs = 0;
                            for (int x = 1; x < 10; x += 2)
                            {
                                cs ^= ASCIIToNum((char)cardID[x], (char)cardID[x + 1]);
                            }
                            if (cs != cardID[11])
                            {
                                HandleChecksumError();
                                break;
                            }

                            OnIDReadyEvent(this, cardID);

                            System.Threading.Thread.Sleep(100);

                            cardID = new string(null);

                            break;
                        }
                    }
                }

                System.Threading.Thread.Sleep(10);
            }
        }

        private void HandleChecksumError()
        {
            serialPort.DiscardInBuffer();//try to recover for next read

            System.Threading.Thread.Sleep(100);

            cardID = new string(null);

            OnIDBadChecksumEvent(this, "ID Checksum Failed");
        }

        /// <summary>
        /// Represents the delegate used for the <see cref="CardIDReceived"/> event.
        /// </summary>
        /// <param name="sender">Senting module.</param>
        /// <param name="ID">ID that was read.</param>
        public delegate void CardIDReceivedEventHandler(RFID sender, String ID);
        
        /// <summary>
        /// Event raised when the module reads a valid ID.
        /// </summary>
        public event CardIDReceivedEventHandler CardIDReceived;
        private CardIDReceivedEventHandler _CardIDReceived;

        /// <summary>
        /// Raises the <see cref="CardIDReceived"/> event.
        /// </summary>
        /// <param name="sender">Sending module.</param>
        /// <param name="ID">ID that was read.</param>
        protected virtual void OnIDReadyEvent(RFID sender, String ID)
        {
            string id = ID.Substring(1, 10);

            if (_CardIDReceived == null) _CardIDReceived = new CardIDReceivedEventHandler(OnIDReadyEvent);

            if (Program.CheckAndInvoke(CardIDReceived, _CardIDReceived, sender, ID))
            {
                this.CardIDReceived(sender, id);
            }

        }

        /// <summary>
        /// Represents the delegate used for the <see cref="CardIDBadChecksum"/> event.
        /// </summary>
        /// <param name="sender">Sending module.</param>
        /// <param name="ID">Error text.</param>
        public delegate void CardIDBadChecksumEventHandler(RFID sender, String ID);

        /// <summary>
        /// Event raised when the module reads an invalid or the checksum does not match.
        /// </summary>
        public event CardIDBadChecksumEventHandler CardIDBadChecksum;
        private CardIDBadChecksumEventHandler _CardIDBadChecksum;

        /// <summary>
        /// Raises the <see cref="CardIDBadChecksum"/> event.
        /// </summary>
        /// <param name="sender">Sending module.</param>
        /// <param name="ID">Error text.</param>
        protected virtual void OnIDBadChecksumEvent(RFID sender, String ID)
        {
            string id = ID.Substring(1, 10);

            if (_CardIDBadChecksum == null) _CardIDBadChecksum = new CardIDBadChecksumEventHandler(OnIDBadChecksumEvent);

            if (Program.CheckAndInvoke(CardIDBadChecksum, _CardIDBadChecksum, sender, ID))
            {
                this.CardIDBadChecksum(sender, id);
            }

        }

    }
}
